﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;

namespace ElGamalPublicEncryption
{
    public class Gamal
    {
        public static readonly int KeySize = 512;
        public BigInteger P;
        public BigInteger G;
        public BigInteger Y;
        public BigInteger Ypair;
        public BigInteger X;
        public BigInteger Kab;

        public Gamal()
        {
            Random pseudoRandom = new Random();
            P = BigInteger.genPseudoPrime(KeySize, 16, pseudoRandom);
            G = new BigInteger(0);
            G.genRandomBits(KeySize - 8, pseudoRandom);
            
            RenewPrivateKey();
        }

        public Gamal(PublicKey publicKey)
        {
            this.SetPublicKey(publicKey);
            RenewPrivateKey();
        }

        #region Keys Setters/Getters
        public PublicKey GetPublicKey()
        {
            return new PublicKey
                       {
                           G = this.G.getBytes(),
                           P = this.P.getBytes(),
                           Y = this.Y.getBytes()
                       };
        }

        public void SetPublicKey(PublicKey publicKey)
        {
            this.G = new BigInteger(publicKey.G);
            this.P = new BigInteger(publicKey.P);
            RenewPrivateKey();
            SetPairKey(publicKey.Y);
        }

        public void SetPairKey(byte[] yPair)
        {
            this.Ypair = new BigInteger(yPair);
            UpdateSharedK();
        }

        private void RenewPrivateKey()
        {
            Random pseudoRandom = new Random();
            X = new BigInteger(0);
            Y = new BigInteger(0);

            X.genRandomBits(KeySize - 8, pseudoRandom);
            Y = G.modPow(X, P);
        }

        public void UpdateSharedK()
        {
            Kab = Ypair.modPow(X, P);
        } 
        #endregion

        #region Validation
        public bool AreValidKeys(out string error)
        {
            //if (this.X == null)
            //{
            //    error = "X is empty";
            //    return false;
            //}
            //if (this.Y == null)
            //{
            //    error = "Y is empty";
            //    return false;
            //}
            //if (this.G == null)
            //{
            //    error = "G is empty";
            //    return false;
            //}
            //if (this.P == null)
            //{
            //    error = "P is empty";
            //    return false;
            //}
            //if (this.Ypair == null)
            //{
            //    error = "Ypair is empty";
            //    return false;
            //}
            //if (this.Kab == null)
            //{
            //    error = "Kab is empty";
            //    return false;
            //}
            error = string.Empty;
            return true;
        } 
        #endregion

        #region Enc/Dec
        public void Encrypt(byte[] message, out string error, out BigInteger c1, out BigInteger c2)
        {
            c1 = null;
            c2 = null;
            
            if (!this.AreValidKeys(out error))
            {
                return;
            }

            BigInteger K;
            c1 = new BigInteger(0);
            c2 = new BigInteger(0);

            K = Ypair.modPow(Kab, P);
            c1 = G.modPow(K, P);
            c2 = (Kab*new BigInteger(message))%P;
        }

        public void Decrypt(BigInteger c1, BigInteger c2, out string error, out byte[] message)
        {
            message = null;
            if (!this.AreValidKeys(out error))
            {
                return;
            }

            BigInteger K, m;

            K = c1.modPow(X, P);
            m = c2 / K;
            message = m.getBytes();
        } 
        #endregion


    }

    [DataContract]
    public class PublicKey
    {
        [DataMember]
        public byte[] P;

        [DataMember]
        public byte[] G;

        [DataMember]
        public byte[] Y;
    }

    class Demo
    {
        public static void Main(string[] args)
        {
            PublicKey publicKey = ServerInit();

            Gamal server = new Gamal();
            Gamal client = new Gamal(server.GetPublicKey());
            server.SetPairKey(client.GetPublicKey().Y);

            server.P = 23;
            client.P = 23;

            server.G = 5;
            client.G = 5;

            server.X = 6;
            client.X = 6;

            server.Y = server.G.modPow(server.X, server.P);
            client.Y = client.G.modPow(client.X, client.P);

            server.Ypair = client.Y;
            client.Ypair = server.Y;

            server.UpdateSharedK();
            client.UpdateSharedK();

            byte[] MMM = new byte[]{9};//Encoding.Default.GetBytes("TesT");
            BigInteger c1, c2;
            byte[] m;
            string error;
            server.Encrypt(MMM, out error, out c1, out c2);
            client.Decrypt(c1, c2, out error, out m);
            Console.WriteLine(CompareArrays(MMM, m));
        }

        private static PublicKey ServerInit()
        {
            Gamal gamal = new Gamal();
            PublicKey publicKey = gamal.GetPublicKey();
            return publicKey;
        }

        private static bool CompareArrays(byte[] p_arr1, byte[] p_arr2)
        {
            for (int i = 0; i < p_arr1.Length; i++)
            {
                if (p_arr1[i] != p_arr2[i])
                {
                    return false;
                }
            }
            return true;
        }
    }
}
